﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Macro
{
    class globalMouseHook
    {
        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct tagMOUSEHOOKSTRUCT
        {
            /// POINT->tagPOINT
            public Point pt;

            /// HWND->HWND__*
            public System.IntPtr hwnd;

            /// UINT->unsigned int
            public uint wHitTestCode;

            /// ULONG_PTR->unsigned int
            public uint dwExtraInfo;
        }

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct Point
        {
            /// LONG->int
            public int x;

            /// LONG->int
            public int y;
        }

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct HWND__
        {
            /// int
            public int unused;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
        //callback delegate is made a member variable of the class to prevent the GC from collecting it
        //otherwise this delegate could be deleted by the GC, since no object is keeping a reference to it
        //it is after all only used by a function that comes from an unmanaged dll
        private static LowLevelMouseProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        //disable constructor
        private globalMouseHook()
        {
        }
        public static void start()
        {
            _hookID = SetHook(_proc);
        }
        public static void stop()
        {
            UnhookWindowsHookEx(_hookID);
        }

        //set callbacks
        private static mouseCallback callback = null;
        public static void setMouseCallback(mouseCallback cb)
        {
            callback = cb;
        }

        private static IntPtr SetHook(LowLevelMouseProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                const int WH_MOUSE_LL = 14;
                return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            const int WM_LBUTTONDOWN = 0x0201;
            const int WM_LBUTTONUP = 0x0202;
            const int WM_MOUSEMOVE = 0x0200;
            const int WM_MOUSEWHEEL = 0x020A;
            const int WM_RBUTTONDOWN = 0x0204;
            const int WM_RBUTTONUP = 0x0205;

            if (nCode >= 0)
            {
                bool lbuttonDown = (wParam == (IntPtr)WM_LBUTTONDOWN);
                bool lbuttonUp = (wParam == (IntPtr)WM_LBUTTONUP);
                bool rbuttonDown = (wParam == (IntPtr)WM_RBUTTONDOWN);
                bool rbuttonUp = (wParam == (IntPtr)WM_RBUTTONUP);
                bool mouseMove = (wParam == (IntPtr)WM_MOUSEMOVE);

                bool isCallbackNeeded = lbuttonDown || lbuttonUp || rbuttonDown || rbuttonUp || mouseMove;
                if (isCallbackNeeded)
                {
                    tagMOUSEHOOKSTRUCT hookStruct = (tagMOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(tagMOUSEHOOKSTRUCT));
                    callback(hookStruct.pt.x, hookStruct.pt.y, lbuttonDown, lbuttonUp, rbuttonDown, rbuttonUp, mouseMove);
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
}
